function reactive (target) {
  return new Proxy(target, {
    get (target, key, receiver) {
      track(target, key) // 依赖追踪
      return Reflect.get(target, key)
    },

    set (target, key, value, receiver) {
      const flag = Reflect.set(target, key, value)
      trigger(target, key) // 触发
      return flag
    }
  })
}

const targetMap = new WeakMap()
let activeEffect

const effectStack = []

const jobQueue = new Set()
const p = Promise.resolve()
let isFlushing = false

function flushJob () {
  if (isFlushing) return
  isFlushing = true
  p.then(() => {
    jobQueue.forEach(job => job())
  }).finally(() => {
    isFlushing = false
  })
}

function cleanup (effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i]
    deps.delete(effectFn)
  }
  effectFn.deps.length = 0
}

function effect (fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    const res = fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    return res
  }

  effectFn.deps = []
  effectFn.options = options
  
  if (!options.lazy) {
    effectFn()
  }

  return effectFn
}

function track (target, key) {
  if (!activeEffect) return

  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }

  let deps = depsMap.get(key)
  if (!deps) {
    depsMap.set(key, (deps = new Set()))
  }

  deps.add(activeEffect)

  activeEffect.deps.push(deps) // 存储当前函数所有副作用set集合
}

function trigger (target, key) {
  const depsMap = targetMap.get(target)

  if (!depsMap) return

  const effects = depsMap.get(key)

  const effectsToRun = new Set()
  effects && effects.forEach(effectFn => {
    if (effectFn !== activeEffect) {
      effectsToRun.add(effectFn)
    }
  })
  effectsToRun.forEach(effectFn => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

function computed (getter) {
  let value
  let dirty = true
  const obj = {
    get value () {
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, 'value')
      return value
    }
  }
  const effectFn = effect(getter, {
    lazy: true,
    scheduler () {
      dirty = true
      trigger(obj, 'value')
    }
  })

  return obj
}

function traverse (value, seen = new Set()) {
  if (typeof value !== 'object' || value === null || seen.has(value)) return
  seen.add(value)

  for (const key in value) {
    traverse(value[key], seen)
  }

  return value
}

function watch (source, cb) {
  let getter
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }
  let oldValue, newValue
  const effectFn = effect(getter, {
    lazy: true,
    scheduler () {
      newValue = effectFn()
      cb(newValue, oldValue)
      oldValue = newValue
    }
  })

  oldValue = effectFn()
}

const obj = reactive({
  a: 1,
  b: 2,
})

const objComputed = computed(() => obj.a + obj.b)

watch(() => objComputed.value, (val, old) => {
  console.log('watch', val, old)
})

obj.a = 2
obj.a = 3


